fastjson 漏洞成因

  1. Fastjson提供了反序列化功能,允许用户在输入JSON串时通过”@type”键对应的value指定任意反序列化类名.
  2. Fastjson自定义的反序列化机制会使用反射生成上述指定类的实例化对象,并自动调用该对象的setter方法及部分getter方法.

补丁对比

通过https://github.com/alibaba/fastjson/compare/1.2.68%E2%80%A61.2.69 查看补丁的更新信息可以看到。

在src/main/java/com/alibaba/fastjson/parser/ParserConfig.java中将原明文存储的期望类经自定义消息摘要函数转换为hash值并且新增了几个class对象。

这里借用带头老哥的图

可以看到增加了对expectClass类,新增了三个方法分别为java.lang.Runnable、java.lang.Readable、java.lang.AutoCloseable

AutoType

在Fastjson中存在AutoType这个东西,目的是为了防止进行恶意反序列化对象从而导致的安全问题,如果在没有启用AutoType的情况下默认是只有白名单以及一些基础类型可以被反序列化。在fastjson指定了@type后,主要用checkAutoType方法检测是否开了autotype开关。

checkAutoType主要对传入的expectClass进行了判断,当safeMode模块没有开启时,如果传入的期望类不null,切不在对比的名单中就会将expectClass的值设置为True。

随后将传入的类进入白名单对比看是否存在,如果不存在,也就是类属于黑名单,并且autoType开启或者,expectClassFlag的值被设置成true就将类加入缓存中。

随后就是一些判断,并将符合条件的类加入到缓存mapping中,fastjson判断autotype的时候会先对mapping的内容进行判断。

根据代码可知经过autotype校验的情况为:

1、白名单里的类
2、开启了autotype
3、使用了jsonType注解
4、指定了期望的类
5、缓存中的mapping类

本次漏洞利用的就是指定了期望的类,可以看到如果通过了上面的判断,切指定的期望类不为空,就会将该类添加到缓存mapping中并且返回该类的class。查找反序列化expectClass的子类或实现,如果构造方法或setter中含有其它类型可重复第一步构造一个反序列化链,直到找到可以利用的类为止。

漏洞分析

前提说到进过autotype的校验有一种情况是输入的类为缓存中的mapping类。可以看到1.2.69修复后expectClass新加的对比类java.lang.AutoCloseable刚好在名单中。

当期望类传入切满足autotype的检测判断后会加入mapping类,然而咋在加入map池的前面对传入的期望类进行了判断。

这里我们可以看到isAssignableFrom()这样一个方法,这个方法用于判断里面的类是否为继承类。也就是说如果我们利用了java.lang.AutoCloseable这个方法去攻击fastjson,那么后续反序列化的链路一定是要继承于该类的子类。

这里简单抄了一个payload过来进一步分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package fastjsonRce.Fj68;

import java.io.IOException;

public class Test implements AutoCloseable{

public Test(String cmd){
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void close() throws Exception {

}
}

加载payload,然后在checkAutoType的入口下断点

1
{"@type":"java.lang.AutoCloseable", "@type":"fastjsonRce.Fj68.Test", "cmd":"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}

这里可以看到第一次进来解析的类为java.lang.AutoCloseable,由于该类存在与mapping池中,所以期望类为空值。

随后将该类进行了反序列化,切该类使用的是JavaBeanDeserializer反序列化器,由于序列化的对象为接口,所以在使用JavaBeanDeserializer会继续解析下一个json字段。

可以看到进行了第二次的checkAutoType,并且将java.lang.AutoCloseable作为了其期望类

由于新传入的类不在mapping池中,但expectClassFlag的值被设置成了true进入了如下判断。将该类加入到了mapping池中

随后进入了expectClass != null的判断。这里的期望类为java.lang.AutoCloseable,因此这也就是之前说的为什么攻击的payload需要继承该类。

随后返回的clazz被赋值为我们的攻击payload Test.class随后使用JavaBeanDeserializer对传入的内容进行了序列化

对与该方法的利用还有一个处罚点就是浅蓝师傅最开始说的ThrowableDeserializer,由于该方法是抛出异常时处罚的,切没有开发人员会将执行命令,lookup等方法写到异常处理的地方,因此这个方法不适用于寻找gadget。这里也将主要存在点附上,代码没截全有兴趣的可以自行探索。

总结

对于该反序列化的挖取其方法需要继承java.lang.AutoCloseable、java.lang.Runnable、java.lang.Readable中的一个类,但是其中部分类的使用被做了限制:

如果类继承与上面几种接口,则会直接抛出异常。

另外一种思路就是根据浅蓝师傅的文章去寻找文件读写。